package service

import (
	"batbat-backend/app/dao"
	"batbat-backend/app/model"
	"context"
	"fmt"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gconv"
	"github.com/gogf/gf/util/grand"
	"golang.org/x/crypto/bcrypt"
)

// User 用户服务
var User = userService{}

type userService struct{}

// Register 用户注册
func (u *userService) Register(ctx context.Context, phoneNumber string, password string,
	inviteCode string) (user *model.User, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var ic *model.InviteCode
		// 查询内测码是否存在或被使用过, 如果存在就对其加锁(select for update)
		err := dao.InviteCode.Ctx(ctx).TX(tx).
			Fields(dao.InviteCode.Columns.Id).
			Where(dao.InviteCode.Columns.Code, inviteCode).
			Where(dao.InviteCode.Columns.Uid, 0). // uid的默认值为0
			LockUpdate().
			Scan(&ic)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("注册失败")
		}
		if ic == nil { // 未查询到对应的内测码
			return gerror.Newf("内测码不存在或已被使用: %v", inviteCode)
		}

		// 判断手机号码是否已经注册
		count, err := dao.User.Ctx(ctx).TX(tx).Where(dao.User.Columns.PhoneNumber, phoneNumber).Count()
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("注册失败")
		}
		if count > 0 {
			return gerror.Newf("手机号已被使用: %v", phoneNumber)
		}

		// 将密码使用bcrypt加密
		hash, err := bcrypt.GenerateFromPassword([]byte(password), 14)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("密码[%v]加密失败: %v", password, err)
			return gerror.Newf("密码加密失败")
		}

		// 保存用户的用户名和密码
		user = &model.User{
			// 默认用户名为"bater_"+手机号码后四位+4位随机字符串
			Username:     fmt.Sprintf("bater_%v%s", phoneNumber[len(phoneNumber)-4:], grand.S(4)),
			Password:     string(hash), // 保存加密后的密码
			PhoneNumber:  phoneNumber,
			Avatar:       model.DefaultAvatar, // 使用默认的头像
			Introduction: "Hello Batbat",
		}
		id, err := dao.User.Ctx(ctx).TX(tx).
			Fields(dao.User.Columns.Username,
				dao.User.Columns.Password,
				dao.User.Columns.PhoneNumber,
				dao.User.Columns.Avatar,
				dao.User.Columns.Introduction).
			InsertAndGetId(user)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("插入数据库发生错误: %v", err)
			return gerror.New("注册失败")
		}
		// 设置用户id
		user.Id = gconv.Uint(id)
		ic.Uid = user.Id

		// 将邀请码设置为已被使用
		_, err = dao.InviteCode.Ctx(ctx).TX(tx).Fields(dao.InviteCode.Columns.Uid).WherePri(ic.Id).Update(ic)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("修改数据库发生错误: %v", err)
			return gerror.New("注册失败")
		}

		// 返回用户
		err = dao.User.Ctx(ctx).TX(tx).Fields(dao.User.Columns).WherePri(user.Id).Scan(&user)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("注册失败")
		}
		return nil
	})
	return user, err
}

// GetInformation 获取用户个人资料
func (u *userService) GetInformation(ctx context.Context, id uint) (user *model.User, err error) {
	err = dao.User.Ctx(ctx).Fields(dao.User.Columns).WherePri(id).Scan(&user)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
		return nil, gerror.New("获取用户个人资料失败")
	}
	return user, nil
}

// ModifyInformation 修改用户个人资料
func (u *userService) ModifyInformation(ctx context.Context,
	req *model.UserApiModifyInformationReq) (uctx *model.UserContext, err error) {
	// 定义返回的错误
	gerr := gerror.New("修改用户个人资料失败")

	// 将请求转换为用户对象
	var user *model.User
	err = gconv.Scan(req, &user)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("将请求转换为用户对象失败: %v", err)
		return nil, gerr
	}
	// 获取要修改的用户的id
	err = gconv.Scan(ctx.Value(model.UserContextKey), &uctx)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("获取要修改的用户id失败: %v", err)
		return nil, gerr
	}

	// 开启事务, 保存用户对象
	user.UpdateTime = gtime.Now()
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		_, err := dao.User.Ctx(ctx).TX(tx).
			Fields(dao.User.Columns.UpdateTime,
				dao.User.Columns.Username,
				dao.User.Columns.Avatar,
				dao.User.Columns.Birthday,
				dao.User.Columns.Gender,
				dao.User.Columns.Introduction).
			WherePri(uctx.Id).
			Update(user)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("修改数据库失败: %v", err)
			return gerr
		}

		err = dao.User.Ctx(ctx).TX(tx).Fields(dao.User.Columns).WherePri(uctx.Id).Scan(&user)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
			return gerr
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	err = gconv.Scan(user, &uctx)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("将用户对象转为用户上下文对象失败")
		return nil, gerr
	}
	return uctx, nil
}

// GetUser 获取一个用户的公开资料
func (u *userService) GetUser(ctx context.Context, id uint) (*model.UserApiGetUserResp, error) {
	var (
		resp = new(model.UserApiGetUserResp)
		user *model.User
	)
	err := g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		// 获取用户
		err := dao.User.Ctx(ctx).TX(tx).Fields(dao.User.Columns).WherePri(id).Scan(&user)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("获取用户资料失败")
		}
		if user == nil {
			return gerror.Newf("用户[%d]不存在", id)
		}
		// 获取用户收藏的游戏
		resp.Favor, err = dao.GameFavor.Ctx(ctx).TX(tx).
			Where(dao.GameFavor.Columns.Uid, user.Id).WhereNull(dao.GameFavor.Columns.DeleteTime).Count()
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("获取用户资料失败")
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	// 将用户类型转换成用户上下文
	err = gconv.Scan(user, &resp.UserContext)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("将用户对象转换成用户上下文对象失败: %v", err)
		return nil, gerror.New("获取用户资料失败")
	}
	return resp, nil
}

// GetUserFavor  获取用户个人收藏的游戏
func (u *userService) GetUserFavor(ctx context.Context, id uint) (resp *model.UserApiGetUserFavorResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var games []*model.Game
		err := dao.Game.Ctx(ctx).TX(tx).
			Fields(dao.Game.Columns).
			WhereIn(dao.Game.Columns.Id, dao.GameFavor.Ctx(ctx).TX(tx).
				Fields(dao.GameFavor.Columns.Gid).
				Where(dao.GameFavor.Columns.Uid, id).
				WhereNull(dao.GameFavor.Columns.DeleteTime)).
			Scan(&games)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("获取收藏的游戏失败")
		}
		resp = new(model.UserApiGetUserFavorResp)
		resp.List = make([]*model.GameResp, len(games))
		for i, game := range games {
			resp.List[i], err = Game.getGameHelper(ctx, game)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询游戏发生错误: %v", err)
				return gerror.New("获取收藏的游戏失败")
			}
		}
		return nil
	})
	return resp, err
}

// Reviews 获取用户的评论
func (u *userService) Reviews(ctx context.Context, id uint, pn int, limit int) (
	resp *model.UserApiReviewsResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		resp = &model.UserApiReviewsResp{
			Page: model.Page{
				Prev:  pn - 1,
				Limit: limit,
			},
			List: make([]*model.UserReview, 0),
		}
		// 计算总数
		count, err := dao.GameReview.Ctx(ctx).TX(tx).Where(dao.GameReview.Columns.Uid, id).Count()
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
			return gerror.New("获取评论失败")
		}
		resp.Count = count
		// 计算下一页
		if pn*limit >= count {
			resp.Next = 0
		} else {
			resp.Next = pn + 1
		}
		// 获取当前页评论
		var reviews []*model.GameReview
		err = dao.GameReview.Ctx(ctx).TX(tx).
			Fields(dao.GameReview.Columns).
			Where(dao.GameReview.Columns.Uid, id).
			WhereNull(dao.GameReview.Columns.DeleteTime).
			Page(pn, limit).
			Scan(&reviews)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
			return gerror.New("获取评论失败")
		}
		if reviews == nil || len(reviews) == 0 {
			if pn == 1 {
				return nil
			} else {
				return gerror.New("获取评论失败, 请校验参数`pn`或`limit`")
			}
		}
		// 获取详情
		resp.List = make([]*model.UserReview, len(reviews))
		for i, r := range reviews {
			// 获取评论详情
			re, err := GameReview.getGameReviewRespHelper(ctx, r)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
				return gerror.New("获取评论失败")
			}
			resp.List[i] = &model.UserReview{Review: re}
			// 获取评论游戏
			var ga *model.Game
			err = dao.Game.Ctx(ctx).TX(tx).Fields(dao.Game.Columns).WherePri(r.Gid).Scan(&ga)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库失败")
				return gerror.New("获取评论失败")
			}
			resp.List[i].Game, err = Game.getGameHelper(ctx, ga)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库失败")
				return gerror.New("获取评论失败")
			}
		}
		return nil
	})
	return resp, err
}
